Перейти к основному содержимому

Указатели и строки

Строки как массивы символов

Строка в языке Си — это массив символов, заканчивающийся нулевым символом '\0'. Указатели позволяют эффективно работать со строками.

char name[] = "Анна";     // Массив: ['А', 'н', 'н', 'а', '\0']
char *namePtr = name; // Указатель на первый символ

Объявление строк через указатели

Два способа создания строк

#include <stdio.h>

int main() {
char greeting[20] = "Привет"; // Массив символов
char *greetingPtr = greeting;

printf("Строка: %s\n", greeting);
printf("Через указатель: %s\n", greetingPtr);

// Можем изменять символы
greeting[0] = 'п'; // Изменяем первый символ
printf("После изменения: %s\n", greeting);

return 0;
}

Обход строки указателем

Посимвольный доступ

#include <stdio.h>

int main() {
char word[] = "Программа";
char *ptr = word;

printf("Символы строки:\n");

while (*ptr != '\0') { // До нулевого символа
printf("'%c' ", *ptr);
ptr++; // Переходим к следующему символу
}
printf("\n");

return 0;
}

Подсчет длины строки

#include <stdio.h>

int stringLength(char *str) {
int length = 0;
while (*str != '\0') {
length++;
str++;
}
return length;
}

int main() {
char text[] = "Изучаем указатели";
int len = stringLength(text);

printf("Строка: \"%s\"\n", text);
printf("Длина: %d символов\n", len);

return 0;
}

Копирование строк

Функция копирования

#include <stdio.h>

void copyString(char *destination, char *source) {
while (*source != '\0') {
*destination = *source; // Копируем символ
destination++;
source++;
}
*destination = '\0'; // Добавляем завершающий нулевой символ
}

int main() {
char original[] = "Исходная строка";
char copy[50]; // Достаточно места для копии

copyString(copy, original);

printf("Оригинал: %s\n", original);
printf("Копия: %s\n", copy);

return 0;
}

Сравнение строк

#include <stdio.h>

int compareStrings(char *str1, char *str2) {
while (*str1 != '\0' && *str2 != '\0') {
if (*str1 != *str2) {
return 0; // Строки разные
}
str1++;
str2++;
}

// Проверяем, что обе строки закончились одновременно
return (*str1 == '\0' && *str2 == '\0');
}

int main() {
char password[] = "secret123";
char input[] = "secret123";

if (compareStrings(password, input)) {
printf("✅ Пароли совпадают\n");
} else {
printf("❌ Пароли не совпадают\n");
}

return 0;
}

Массив строк

Указатели на строки

#include <stdio.h>

int main() {
char *cities[4] = {
"Москва",
"Санкт-Петербург",
"Новосибирск",
"Екатеринбург"
};

printf("Крупнейшие города России:\n");

for (int i = 0; i < 4; i++) {
printf("%d. %s\n", i + 1, cities[i]);
}

// Работаем с отдельной строкой
char *selectedCity = cities[1];
printf("\nВыбранный город: %s\n", selectedCity);
printf("Первая буква: '%c'\n", *selectedCity);

return 0;
}

Модификация строк через указатели

Изменение символов

#include <stdio.h>

void toUpperCase(char *str) {
while (*str != '\0') {
if (*str >= 'a' && *str <= 'z') {
*str = *str - 'a' + 'A'; // Преобразуем в заглавную
}
str++;
}
}

int main() {
char text[] = "hello world"; // Изменяемая строка

printf("До преобразования: %s\n", text);

toUpperCase(text);

printf("После преобразования: %s\n", text);

return 0;
}

Удаление символов

#include <stdio.h>

void removeSpaces(char *str) {
char *src = str; // Источник (откуда читаем)
char *dst = str; // Назначение (куда пишем)

while (*src != '\0') {
if (*src != ' ') { // Если не пробел
*dst = *src; // Копируем символ
dst++;
}
src++;
}
*dst = '\0'; // Завершаем строку
}

int main() {
char sentence[] = "У б и р а е м п р о б е л ы";

printf("До обработки: \"%s\"\n", sentence);

removeSpaces(sentence);

printf("После обработки: \"%s\"\n", sentence);

return 0;
}

Практические функции для строк

Поиск подстроки

#include <stdio.h>

char* findSubstring(char *text, char *pattern) {
char *textPtr = text;

while (*textPtr != '\0') {
char *t = textPtr; // Начинаем сравнение с текущей позиции
char *p = pattern; // Начинаем с начала образца

// Сравниваем символы
while (*t != '\0' && *p != '\0' && *t == *p) {
t++;
p++;
}

if (*p == '\0') { // Образец полностью совпал
return textPtr;
}

textPtr++; // Переходим к следующему символу в тексте
}

return NULL; // Не найдено
}

int main() {
char document[] = "Язык программирования Си";
char search[] = "программ";

char *result = findSubstring(document, search);

if (result != NULL) {
int position = result - document;
printf("Найдено \"%s\" в позиции %d\n", search, position);
printf("Контекст: ...%s\n", result);
} else {
printf("Подстрока \"%s\" не найдена\n", search);
}

return 0;
}

Различия в работе со строками

Строковые литералы vs массивы

#include <stdio.h>

int main() {
// Массив символов (изменяемый)
char editableString[] = "Можно изменить";
char *editPtr = editableString;

// Строковый литерал (неизменяемый)
char *literalPtr = "Нельзя изменить";

printf("Изменяемая строка: %s\n", editableString);
printf("Литерал: %s\n", literalPtr);

// ✅ Можно изменить массив
*editPtr = 'м'; // Первая буква становится строчной
printf("После изменения: %s\n", editableString);

// ❌ Нельзя изменить литерал
// *literalPtr = 'н'; // Ошибка выполнения!

// ✅ Можно переназначить указатель на литерал
literalPtr = "Другая строка";
printf("Новый литерал: %s\n", literalPtr);

return 0;
}
Важные различия
  • Массив символов — изменяемые данные в памяти
  • Строковый литерал — неизменяемые данные, обычно в read-only памяти
  • Попытка изменить строковый литерал может вызвать сбой программы
Практические советы
  • Используйте массивы символов для строк, которые нужно изменять
  • Строковые литералы подходят для константных сообщений
  • При работе с указателями всегда проверяйте наличие '\0'
  • Помните: strlen() не включает завершающий нулевой символ

Указатели предоставляют мощные инструменты для эффективной обработки строк посимвольно.